<?php
/**
 * 模板操作类
 * 
 * @example
 * 
 * 字符串类型标签
 * {title/}
 *
 * URL参数中获取值的标签
 * {get.title/}
 *
 * POST参数中获取值的标签
 * {post.title/}
 * 
 * 语言包中获得值
 * {lang.common.title/}
 *
 * 数据库中返回单一值
 * {db.one sql="select count(*) form myTable"/}
 *
 * 数据库中返回数组
 * {db sql="select * form myTable}
 *	 ..
 * {/db}
 *
 * 数组类型标签
 * {article type="xxx"}
 *		{title size="3"/}
 *		{keywords/}
 * {/article}
 * 
 */

class template{

	/**
	 * @var string 文件中加载模板
	 */
	public $load_file;

	/**
	 * @var string 从字符串中加载模板
	 */
	public $load_string;

	/**
	 * @var array 模板替换数组
	 */
	public $assign;

	/**
	 * @var string 加载的模板目录，默认调用的是PATH_TEMPLATE中指定的目录
	 */
	public $path;

	//临时值传递
	private $mAssign;

	/**
	 * 初始化模板操作类
	 */
	public function __construct(){
		//$this->path=PATH_TEMPLATE;
	}

	/**
	 * 模板解析输出
	 *
	 * @return string 解析后的模板代码
	 */
	public function output(){
		//如果定义的是文件路径，则从文件中读取
		if (!empty($this->load_file)) {
			$filepath=ROOT.$this->path.'/'.$this->load_file;
			//如果文件存在，则读取
			if (is_file($filepath)) {
				$load_string=file_get_contents($filepath);
				$parent="/<\?php\s+exit;?\s+\?>\s*/";
				$this->load_string=preg_replace($parent,'',$load_string);
			}else{
				kc_tip(kc_lang('template.notfile',array('filepath'=>$this->path.'/'.$this->load_file)));
			}
		}

		$s='';

		//预处理include对象页面
		$parent='%(<!--\s*)?\{include\s+file\=(["\'])(.*?)\2\s*\/\}(\s*-->)?%s';
		$this->load_string=preg_replace_callback($parent,array(&$this,'regexcallbackinclude'),$this->load_string);

		//过滤注释
		$parent='%(<!--\s*)?\{!--.+?--\}(\s*-->)?%s';
		$this->load_string=preg_replace($parent,'',$this->load_string);

		//当页面第一个定义了{Tags}的时候，输出当前可用标签的内容
		if (substr($this->load_string,0,6)=='{Tags}') {
			$s='<table style="border:5px solid #CCC;background:#EFEEEE;padding:15px;line-height:20px;">';
			foreach($this->assign as $key => $val){
				$s.="<tr><th>{{$key}/}</th><td>$val</td></tr>";
			}
			$s.='</table>';
		}else{
			//解析标签
			$parent='#(<!--\s*)?\{([\w\.]+)(\s[^}]*)?/\}(\s*-->)?|(<!--\s*)?\{([\w\.]+)(\s[^}]*)?\}(\s*-->)?(((?R)|[^{}]|\{\w+(\s[^}]*)?\})*)(<!--\s*)?\{/\6\}(\s*-->)?#is';
			$s=preg_replace_callback($parent,array(&$this,'regexcallback'),$this->load_string);
		}

		//清空读取值，这样一个模板连续调用的时候，不用多次读取
		$this->load_file='';

		return $s;
	}
	
	/**
	 * 预先解析include标签
	 * @param array $m
	 * @return string
	 */
	private function regexcallbackinclude($m){
		$filepath=ROOT.$this->path.'/'.$m[3];
		$s='';
		if (is_file($filepath)) {
			$s=file_get_contents($filepath);
			$parent="/<\?php\s+exit;?\s+\?>\s*/";
			$s=preg_replace($parent,'',$s);

		}else{
			$s="<!-- include file=".$this->path."{$m[3]} -->";
		}
		return $s;
	}

	/**
	 * 解析PHP代码
	 * @param array $m  PHP代码内容
	 * @return string
	*/
	private function regexcallbackphpcode($m){
		$php=$m[3];

		$s='';

		if(isset($php)){
			ob_start();
			eval($php);
			$s=ob_get_clean();
		}

		return $s;
	}
	
	/**
	 * KingCMS标签解析
	 * @param array $m
	 * @param array $val  传值
	 * @return string
	*/
	private function regexcallback($m,$val=null){

	/**
	 * 模板解析引擎的新功能
	 *
	 * capture 变量捕获
	 * {capture.var}
	 *     这中间的内容存储给coo变量中，以{$var/}方式调用这个值
	 * {/capture.var}
	 * 读到这个标签，并执行后，直接加入到$this->assign
	 *
	 * loadConfig 读取配置文件
	 * {loadConfig file="yyy.conf"/}
	 * 读取这个config文件后，把其值加入到$this->assign里，调用方式是{#var/}
	 *
	 * php可执行代码的编写
	 * <?php
	 * ?>
	 * 除了支持上述方法外，还可以这样写
	 * {php}
	 * {/php}
	 *
	 * $_SERVER，$GLOBALS等系统值的获取
	 * {server.user_agent_client/}
	 * {define.ROOT/}//注意区分大小写
	 * {cookie.auth/}
	 *
	 */


		$ass = &$this->assign;

		$s='';
		if (count($m)<=5) {
			$name=strtolower($m[2]);//完整开头
			$names=explode('.',$name);//拆分
			$prefix=$names[0];//前缀，有可能即为开头一致
			//echo('<pre>'.print_r($m,1)).'</pre>';
			$attributes=kc_val($m,3);//属性值
			$attrib=$this->attrib2array($attributes,$ass);//属性整理成数组

			switch ($prefix) {
				case 'get':
					$s= isset($attrib['validate']) ? kc_get($name,$attrib['validate']) : kc_get($name,0);
					break;
				case 'post':
					$s= isset($attrib['validate']) ? kc_post($name,$attrib['validate']) : kc_post($name);
					break;
				case 'lang':
					$s=kc_lang(substr($name,5));
					break;
				case 'define':
					if (isset($names[1])) {
						$define=strtoupper($names[1]);
						if (defined($define)) {
							$s=constant($define);
						}
					}
					break;
				case 'server':
					$name_upper=strtoupper($names[1]);
					$s= isset($_SERVER[$name_upper]) ? $_SERVER[$name_upper] : '';
					break;
				case 'config':
					$s=kc_config(substr($name,7));
					break;
				default:
					if (isset($ass[$name])) {
						$s=$ass[$name];
					}else{

						if(in_array($name,array('keywords','description'))){
							$s=kc_val($ass,'title');
						}else{
							if (is_file(ROOT."include/lib/$prefix.class.php")) {
								$cls=new $prefix;
								if (method_exists($prefix,'tag')) {
									$s=$cls->tag($name,'',$ass,$attrib);
								}else{
									$s="<!-- $prefix  \n \n".$m[0]."\n -->";
								}
							}elseif($s_temp=$this->sysinfo($name)){
								$s=$s_temp;
							}elseif($name=='pagelist' && isset($ass['CLASS'])){
								$clsName=$ass['CLASS'];
								$cls=new $clsName;
								if (method_exists($clsName,'tagPageList')) {
									$s=$cls->tagPageList($name,'',$ass,$attrib);
								}
							}elseif($name=='nav'){
								$clsName=$ass['CLASS'];
								$cls=new $clsName;
								if (method_exists($clsName,'tagNav')) {
									$inner='<a href="{url/}">{name/}</a>';
									$s=$cls->tagNav($name,$inner,$ass,$attrib);
								}else{
									$s="<!-- $prefix  \n \n".$m[0]."\n -->";
								}
							}else{
								$s='<!-- '.$m[0].' -->';
							}
						}

					}
					break;
			}

		}else{
		//循环方式
			$name=strtolower($m[6]);
			$names=explode('.',$name);
			$prefix=$names[0];
			$attributes=$m[7];
			$attrib=$this->attrib2array($attributes,$ass);
			$inner=$m[9];


			if(isset($ass[$name])){
				//如果直接传递数组的话，无需转换
				$s=$this->array_format($inner,$ass[$name]);
				
			}elseif($prefix=='capture'){//赋值给ass值
				if(!empty($names[1])){
					$capture_var=$names[1];
					$tmp=new template;
					$tmp->assign=$ass;
					$tmp->load_string=$inner;
					$str=$tmp->output();

					$ass[$capture_var]=$str;
				}

			}elseif($prefix=='if'){
				//$attributes
				$conditions=$this->internalValue($attributes,$ass);
				$split=preg_split('/\{else\s*\}/',$inner,2);

				$is=false;//evel内部$is没有和外部一致，出现2级错误
				if(!empty($conditions))
					eval("\$is= ($conditions);");

				if ($is) {
					$inner_if=$split[0];
				}else{
					$inner_if='';
					if (isset($split[1])) {
						$inner_if=$split[1];
					}
				}
				
				$tmp=new template;
				$tmp->assign=$ass;
				$tmp->load_string=$inner_if;
				$s=$tmp->output();
				
			}elseif($name=='pagelist' && isset($ass['CLASS'])){
				$clsName=$ass['CLASS'];
				$cls=new $clsName;
				if (method_exists($clsName,'tagPageList')) {
					$s=$cls->tagPageList($name,'',$ass,$attrib);
				}else{
					$s="<!-- $prefix  \n \n".$m[0]."\n -->";
				}
				
			}elseif($name=='nav'){
				$clsName=$ass['CLASS'];
				$cls=new $clsName;
				if (method_exists($clsName,'tagNav')) {
					$s=$cls->tagNav($name,$inner,$ass,$attrib);
				}else{
					$s="<!-- $prefix  \n \n".$m[0]."\n -->";
				}
			}else{

				//判断对应的类文件是否存在，若存在则调用对应tag
				if (is_file(ROOT.'include/lib/'.$prefix.'.class.php')) {
					$cls = new $prefix;
					//判断对应类中是否存在tag方法
					if (method_exists($prefix,'tag')) {
						$s=$cls->tag($name,$inner,$ass,$attrib);
					}else{
						$s="<!-- 类中未定义方法 tag() -->\n";
						$s.="<!-- $prefix  \n \n".$m[0]."\n -->";
					}
				}else{//如果没有对应的文件的情况下，判断是否为模型前缀
					$modelTables=model::getModelTables();
					//如果是模型前缀
					if (in_array($prefix,$modelTables)) {
						$s=content::tag($name,$inner,$ass,$attrib);
					}elseif($name=='list'){//list是保留字，有冲突
						$s=lists::tag($name,$inner,$ass,$attrib);
					}else{
						$s="<!-- 请求的模型不存在! -->\n";
						$s.="<!-- \n".$m[0]."\n -->";
					}
				}
			}
		}
		return $this->str_format($s,$attrib);
	}
	
	/**
	 * 数组类数据格式化
	 * @param string $inner 循环调用
	 * @param array  $array  数组，结构如下
	 * @example
	 * <code>
	 * $array=array(
	 *	array(
	 *		'kid'=>1,
	 *		'listid'=>2,
	 *	),
	 *	 array(
	 *		'kid'=>2,
	 *		'listid'=>2,
	 *	 ),
	 * );
	 */
	private function array_format($inner,$array){

		$s='';

		if(is_array($array)){
			foreach($array as $arr){
				$tmp=new template;
				$tmp->assign=$arr;
				$tmp->load_string=$inner;
				$s.=$tmp->output();
				/*
				foreach($arr as $key => $val){
					$tmp->assign($key,$val);
				}
				 *
				$s.=$tmp->output($inner);
				 */
			}

		}

		return $s;
	}
	
	/**
	 * 格式化字符串
	 *
	 * @param string $s 字符模板
	 * @paran string $attrib 字符串属性，应该是size="20"这种类型的，具体做的时候还得进行输出判断，attrib可能的取值如下
	 *		width,height : 如果有这两个或一个属性，则对$str进行文件判断，如果是则进行相关图片处理操作
	 *		replace      : 字符串替换，replace="A|B"，A替换为B
	 *		size         : 字符长度设置
	 *		code         : 字符转换js/html
	 *		none         : 空值替换属性
	*/
	private function str_format($s,$attrib){

		if(empty($attrib)) return $s;//如果是空值，则直接返回s值

		//转换
		if(array_key_exists('formatstr',$attrib)){
			$code=$attrib['formatstr'];
			if(isset($code{0})){
				switch(strtolower($code)){
					case 'htmlencode':
						$s=htmlspecialchars($s);
						break;
					case 'html':
						$s=htmlspecialchars($s);
						break;
					case 'javascript':
						$s=str_replace(
							array('\'',"\n",chr(13)),
							array('\\\'','\n','')
						,$s);
						break;
					case 'js':
						$s=str_replace(
							array('\'',"\n",chr(13)),
							array('\\\'','\n','')
						,$s);
						break;
					case 'urlencode':$s=urlencode($s);break;
					case 'addslashes':$s=addslashes($s);break;
					case 'md5':$s=md5($s);break;
				}
			}
		}
		//应用函数
		if(array_key_exists('fun',$attrib)){
			$fun=$attrib['fun'];
			$funs=explode(',',$fun);
			$array=array(1=>$s);
			foreach($funs as $fun){
				if(function_exists($fun)){//如果有指定的函数，则应用
					$array1=array_map($fun,$array);
				}
			}
			$s=$array1[1];
		}
		//替换
		if(array_key_exists('replace',$attrib)){
			$replace=$attrib['replace'];
			if(is_array($replace)){
				foreach($replace as $key => $val){
					$s=str_replace($key,$val,$s);
				}
			}
	/*
			if(isset($replace{0})){
				list($find,$new)=kc_explode('|',$replace,2);
				$s=str_replace($find,$new,$s);
			}
	*/
		}
		//长度
		if(array_key_exists('size',$attrib)){

			$size=$attrib['size'];
			if($size){
				if(kc_validate($size,2)){
					$s=str::substring($s,0,$size);
				}
			}
		}
		//日期格式化
		if(array_key_exists('formatdate',$attrib)){
			$format=$attrib['formatdate'];
			$formtIs=kc_val($attrib,'formatdate_is',0);
			if(kc_validate($s,2)){//默认的时间是int类型的
				$s=str::formatdate($s,$format,$formtIs);
			}elseif(kc_validate($s,9)){//日期类型 2008-11-9这种格式
				list($yy,$mm,$dd)=explode('-',$s);
				$s=str::formatdate(gmmktime(0,0,0,$mm,$dd,$yy),$format,$formatIs);//需要转换一下字符
			}
		}
		//数字格式化
		if(array_key_exists('formatnumber',$attrib)){
			if(kc_validate($attrib['formatnumber'],2)){
				$s=number_format($s,$attrib['formatnumber']);
			}
		}
		//缩略图
		if(array_key_exists('width',$attrib)||array_key_exists('height',$attrib)){
			if(array_key_exists('width',$attrib)) $width=$attrib['width'];
			if(array_key_exists('height',$attrib)) $height=$attrib['height'];

			if(($width ||$height) && isset($s{0})){

				$s=image::thumb($s,$attrib);

			}
		}
		//默认填充
		if(array_key_exists('none',$attrib)){
			$none=$attrib['none'];
			if(!isset($s{0})){
				$s=$none;
			}
		}

		//前面插入
		if(array_key_exists('before',$attrib)){
			$before=$attrib['before'];
			if(isset($before{0}) && isset($s{0})){
				$s=$before.$s;
			}
		}
		//后面插入,条件是$s不能为空
		if(array_key_exists('after',$attrib)){
			$after=$attrib['after'];
			if(isset($after{0}) && isset($s{0})){
				$s.=$after;
			}
		}


		return $s;
	}

	/**
	 * attribute内部的小括号替换为对应的值
	 * @param string $s 字符串
	 * @param array $ass 替换参数
	 * @return string 替换后的字符串
	 */
	private function internalValue($s,$ass){
		if($ass){
			//替换内部标签
			$this->mAssign=$ass;
			$s=preg_replace_callback('%\(([\w\.]+)(\s+([^)]*))?/\)%',array(&$this,'attribBack'),$s);
			$this->mAssign=array();
		}
		return $s;
	}

	/**
	 * 属性转换为数组
	 *
	 * @param string $s    属性 listid="1" type="list"
	 * @param array  $ass  assign数组
	 */
	private function attrib2array($s,$ass=null){
		/*
		if($ass){
			//替换内部标签
			$this->mAssign=$ass;

			$s=preg_replace_callback('%\(([\w\.]+)(\s+([^)]*))?/\)%',array(&$this,'attribBack'),$s);

			$this->mAssign=array();
		}
		 */
		$s=$this->internalValue($s,$ass);

		preg_match_all('/([\w\-!]+)=(["\'])(.*?)\2/', $s, $array, PREG_SET_ORDER);

		$newArray=array();
		if($array){
			foreach($array as $val){
				$key=strtolower($val[1]);
				if($key=='replace'){//支持多个replace功能
					//list($k,$v)
					if (!empty($val)) {
						$val_array=explode('|',$val[3],2);
						$k=$val_array[0];
						$v=empty($val_array[1]) ? '' : $val_array[1];
						$newArray['replace'][$k]=$v;
					}
				}else{
					$newArray[$key]=$val[3];
				}
			}
		}

		return $newArray;

	}

	private function attribBack($m){

		$name=strtolower($m[1]);
		$names=explode('.',$name);
		$prefix=$names[0];
		$attrib=array();

		if (isset($m[2])) {
			$attributes=$m[2];
			$attrib=$this->attrib2array($attributes);
		}
		
		$s='';

		if ($prefix=='get') {
			$validate=kc_val($attrib,'validate');
			$s=kc_get($names[1],(empty($validate) ? 0 : $validate));
		}elseif($prefix=='post') {
			$validate=kc_val($attrib,'validate');
			$s=kc_post($names[1],(empty($validate) ? 0 : $validate));
		}else{
			$str=kc_val($this->mAssign,$name);
			if (is_array($str)) {//判断数据类型
				$s=!empty($str);//print_r($str,1);
			}else{
				$s=$str;
			}
			//$s=kc_val($this->mAssign,$name);
		}

		return $this->str_format($s,$attrib);
	}
	/**
	 * 返回系统信息
	 *
	 * @param string $name 标签名称
	 * @return string
	*/
	private function sysinfo($name){
		if(in_array($name,array('root','version','cms'))){

			switch($name){
				case 'root':$s=PATH;break;
				case 'version':$s=number_format(VERSION/100,2);break;
				case 'cms':$s="<span>Powerd by <a href=\"http://www.kingcms.com/\" title=\"KingCMS\" target=\"_blank\">KingCMS</a> ".number_format(VERSION/100,2)."</span>";break;
			}
			return $s;
		}
		return false;
	}
	/**
	 * 返回%s_conn表信息
	 *
	 * @param string $kname
	 * @return array || false
	*/
	public function infoConn($kname){
		$cachepath='system/conn/info';
		$array=$king->cache->get($cachepath);
		if(!$array){
			$res=$king->db->getRows('%s_conn','kname,urlpath,ksign');
			if($res){
				foreach($res as $rs){
					$array[$rs['kname']]=$rs;
				}
				$king->cache->put($cachepath,$array);
			}
		}

		return isset($array[$kname]) ? $array[$kname] : false;
	}
	/**
	 * getConn
	 *
	 * @param string $kname 调用的链接名称
	 * @return string
	*/
	private function getConn($kname,$tags,$ass){
		if(($info=$this->infoConn($kname))!==false){

			$tags=preg_replace('/conn=(["\'])(.*?)\1/i','',$tags);//

			$array=array(
				'ass'=>base64_encode(serialize($ass)),
				'kname'=>$kname,
				'tags'=>$tags,
			);
			$sign=md5("ass={$array['ass']}&kname=$kname&tags=$tags{$info['ksign']}");
			$array['sign']=$sign;
			return kc_fopen($info['urlpath'].'/api/kc.php',$array);
		}else{
			return '<!-- Not Connect! -->';
		}
	}

}
?>